Python中yield關鍵字的用途是什麼? 例如,我試圖理解此代碼1: def _get_child_candidates(自身,距離,min_dist,max_dist): 如果self._leftchild和距離-max_dist= self._median: 產生self._rightchild 這是呼叫者: 結果,候選人= [],[自身] 而候選人: 節點=候選人.pop() 距離= node._get_dist(obj) 如果距離<= max_dist和距離> = min_dist: result.extend(node._values) 候選人擴展(node._get_child_candidates(距離,min_dist,max_dist)) 返回結果 調用_get_child_candidates方法時會發生什麼? 是否返回列表?一個元素?再叫一次嗎?後續通話什麼時候停止? 1.這段代碼是由Jochen Schulz(jrschulz)編寫的,Jochen Schulz是一個很好的用於度量空間的Python庫。這是完整源代碼的鏈接:模塊mspace。
2020-12-07 13:21:19
1個 2 下一個 要了解產量,您必須了解發電機是什麼。而且,在您了解生成器之前,您必須了解可迭代。 可迭代 創建列表時,可以一一閱讀它的項目。逐一讀取其項稱為迭代: >>> mylist = [1、2、3] >>>對於我在我的列表中: ...打印(i) 1個 2 3 mylist是可迭代的。當您使用列表推導時,您將創建一個列表,因此是可迭代的: >>> mylist = [x * x for x in range(3)] >>>對於我在我的列表中: ...打印(i) 1個 4 您可以在“ for ... in ...”上使用的所有內容都是可迭代的;列表,字符串,文件... 這些可迭代的方法很方便,因為您可以隨意讀取它們,但是您將所有值都存儲在內存中,當擁有很多值時,這並不總是想要的。 發電機 生成器是迭代器,一種只能迭代一次的可迭代器。生成器未將所有值存儲在內存中,而是即時生成值: >>> mygenerator =(x * x表示範圍(3)中的x) >>>對於mygenerator中的我: ...打印(i) 1個 4 除了使用()而不是[]之外,其他功能相同。但是,您不能第二次在mygenerator中執行i,因為生成器只能使用一次:它們先計算0,然後忘記它,然後計算1,最後一次計算4。 產量 yield是一個與return一樣使用的關鍵字,只是該函數將返回生成器。 >>> def createGenerator(): ...我的清單=範圍(3) ...對於我在我的清單中: ...產生i * i ... >>> mygenerator = createGenerator()#創建一個生成器 >>> print(mygenerator)#mygenerator是一個對象! <生成器對象createGenerator位於0xb7555c34> >>>對於mygenerator中的我: ...打印(i) 1個 4 這是一個無用的示例,但是當您知道函數將返回只需要讀取一次的大量值時,它就很方便。 要掌握yield,必須了解當調用函數時,在函數主體中編寫的代碼不會運行。該函數僅返回生成器對象,這有點棘手:-) 然後,您的代碼將在每次使用生成器時都從停止的地方繼續。 現在最困難的部分是: for第一次調用從您的函數創建的生成器對象時,它將從頭開始運行函數中的代碼,直到達到yield為止,然後它將返回循環的第一個值。然後,每個後續調用將運行您在函數中編寫的循環的另一次迭代,並返回下一個值。這將一直持續到生成器被認為是空的為止,這種情況會在函數運行而沒有達到產量時發生。那可能是因為循環已經結束,或者是因為您不再滿足“ if / else”的要求。 您的代碼說明 發電機: #在這裡創建節點對象的方法,該方法將返回生成器 def _get_child_candidates(自身,距離,min_dist,max_dist): #這是每次使用generator對象時將被調用的代碼: #如果節點對象的左邊還有一個子對象 #AND如果距離合適,則返回下一個孩子 如果self._leftchild和距離-max_dist= self._median: 產生self._rightchild #如果函數到達此處,生成器將被視為空 #不超過兩個值:左子代和右子代 呼叫者: #創建一個空列表和一個包含當前對象引用的列表 結果,候選人= list(),[個體] #循環候選(它們在開始時只包含一個元素) 而候選人: #獲取最後一個候選人並將其從列表中刪除 節點=候選人.pop() #獲取obj和候選對象之間的距離 距離= node._get_dist(obj) #如果距離合適,則可以填寫結果 如果距離<= max_dist和距離> = min_dist: result.extend(node._values) #在候選人列表中添加候選人的孩子 #因此循環將一直運行,直到看起來為止 #在候選人的子女等的所有子女中 候選人擴展(node._get_child_candidates(距離,min_dist,max_dist)) 返回結果 此代碼包含幾個智能部分: 循環在一個列表上迭代,但是循環在迭代時列表會擴展:-)這是瀏覽所有這些嵌套數據的一種簡潔方法,即使這樣做有點危險,因為您可能會遇到無限循環。在這種情況下,candidates.extend(node._get_child_candidates(distance,min_dist,max_dist))耗盡了生成器的所有值,但同時仍在繼續創建新的生成器對象,該對象將產生與先前值不同的值,因為它沒有應用於相同的值節點。 extend()方法是一個列表對象方法,該方法需要可迭代並將其值添加到列表中。 通常我們將一個列表傳遞給它: >>>一個= [1、2] >>> b = [3,4] >>> a.extend(b) >>>打印(a) [1、2、3、4] 但是在您的代碼中,它得到了一個生成器,這很好,因為: 您不需要兩次讀取值。 您可能有很多孩子,並且您不希望所有孩子都存儲在內存中。 它之所以有效,是因為Python不在乎方法的參數是否為列表。 Python期望可迭代,因此它將與字符串,列表,元組和生成器一起使用!這就是所謂的鴨子輸入,這是Python如此酷的原因之一。但這是另一個故事,還有另一個問題... 您可以在這裡停止,或者閱讀一點以了解生成器的高級用法: 控制發電機耗盡 >>>類Bank():#讓我們創建一個銀行,構建ATM ...危機=錯誤 ... def create_atm(self): ...雖然不是自我危機: ...產生“ $ 100” >>> hsbc = Bank()#一切正常時,ATM會為您提供所需的一切 >>> corner_street_atm = hsbc.create_atm() >>>打印(corner_street_atm.next()) $ 100 >>>打印(corner_street_atm.next()) $ 100 >>>打印([[corner_street_atm.next()表示範圍(5)中的現金]) ['$ 100','$ 100','$ 100','$ 100','$ 100'] >>> hsbc.crisis = True#危機來了,沒有錢了! >>>打印(corner_street_atm.next()) <類型'exceptions.StopIteration'> >>> wall_street_atm = hsbc.create_atm()#對於新的ATM機甚至如此 >>>打印(wall_street_atm.next()) <類型'exceptions.StopIteration'> >>> hsbc.crisis = False#問題是,即使在危機後,ATM仍然為空 >>>打印(corner_street_atm.next()) <類型'exceptions.StopIteration'> >>> brand_new_atm = hsbc.create_atm()#建立一個新的以恢復營業 >>>兌換brand_new_atm: ...打印現金 $ 100 $ 100 $ 100 $ 100 $ 100 $ 100 $ 100 $ 100 $ 100 ... 注意:對於Python 3,請使用print(corner_street_atm .__ next __())或print(next(corner_street_atm)) 對於諸如控制對資源的訪問之類的各種事情,它可能很有用。 Itertools,您最好的朋友 itertools模塊包含用於操縱可迭代對象的特殊功能。曾經希望復制一個發電機嗎? 連鎖兩個發電機?用一個班輪將值嵌套在嵌套列表中?地圖/郵編而未創建其他列表? 然後只需導入itertools。 一個例子?讓我們看一下四馬比賽的可能到達順序: >>>馬= [1、2、3、4] >>>種族= itertools.permutations(馬) >>>打印(種族) <位於0xb754f1dc的itertools.permutations對象> >>>打印(列表(itertools.permutations(馬))) [(1、2、3、4), (1,2,4,3), (1、3、2、4), (1、3、4、2), (1、4、2、3), (1、4、3、2), (2,1,3,4), (2,1,4,3), (2,3,1,4), (2,3,4,1), (2,4,1,3), (2,4,3,1), (3,1,2,4), (3,1,4,2), (3,2,1,4), (3,2,4,1), (3,4,1,2), (3,4,2,1), (4,1,2,3), (4,1,3,2), (4,2,1,3), (4,2,3,1), (4、3、1、2), (4、3、2、1)] 了解迭代的內部機制 迭代是一個包含可迭代對象(實現__iter __()方法)和迭代器(實現__next __()方法)的過程。 可迭代對像是可以從中獲取迭代器的任何對象。迭代器是使您可以迭代可迭代對象的對象。 本文中有更多關於for循環如何工作的內容。 | 了解產量的捷徑 當您看到帶有yield語句的函數時,請應用以下簡單技巧,以了解將要發生的情況: 在函數的開頭插入一行結果= []。 將每個yield expr替換為result.append(expr)。 在函數的底部插入行返回結果。 是的-沒有更多的收益聲明!閱讀並找出代碼。 將功能與原始定義進行比較。 這個技巧可能會讓您對函數背後的邏輯有所了解,但是yield實際發生的事情與基於列表的方法發生的事情顯著不同。在許多情況下,yield方法也將大大提高內存效率和速度。在其他情況下,即使原始函數運行正常,此技巧也會使您陷入無限循環。請繼續閱讀以了解更多信息... 不要混淆您的Iterable,Iterators和Generators 首先,迭代器協議-當您編寫時 對於mylist中的x: 圈體 Python執行以下兩個步驟: 獲取mylist的迭代器: 調用iter(mylist)->這將返回帶有next()方法的對象(或Python 3中的__next __())。 [這是大多數人忘記告訴您的步驟] 使用迭代器遍歷項目: 繼續在步驟1返回的迭代器上調用next()方法。將next()的返回值分配給x,並執行循環體。如果從next()內部引發異常StopIteration,則意味著迭代器中沒有更多值,並且退出了循環。 事實是Python隨時想要遍歷對象的內容時都執行上述兩個步驟-因此它可能是for循環,但也可能是諸如otherlist.extend(mylist)之類的代碼(其中otherlist是Python列表) 。 這裡的mylist是可迭代的,因為它實現了迭代器協議。在用戶定義的類中,可以實現__iter __()方法以使您的類的實例可迭代。此方法應返回迭代器。迭代器是具有next()方法的對象。可以在同一個類上同時實現__iter __()和next(),並使__iter __()返回self。這將在簡單情況下起作用,但是當您希望兩個迭代器同時在同一個對像上循環時則無效。 這就是迭代器協議,許多對像都實現了該協議: 內置列表,字典,元組,集合,文件。 用戶定義的實現__iter __()的類。 發電機。 請注意,for循環不知道它要處理的是哪種對象-它僅遵循迭代器協議,並且很高興在調用next()時逐項獲取對象。內置列表一一返回其項,字典一一返回鍵,文件一一返回行,依此類推。生成器返回...產生收益的地方是: def f123(): 產量1 產量2 產量3 對於f123()中的項目: 打印項目 如果您在f123()中只有三個return語句,則不會執行yield語句,只有第一個會執行,該函數將退出。但是f123()不是普通函數。調用f123()時,它不會返回yield語句中的任何值!它返回一個生成器對象。另外,該函數並沒有真正退出-進入了掛起狀態。當for循環嘗試循環生成器對象時,該函數從先前返回的yield之後的下一行開始從其掛起狀態恢復,執行下一行代碼(在本例中為yield語句),並返回作為下一項。這會一直發生,直到函數退出,此時生成器將引發StopIteration,然後循環退出。 因此,生成器對像有點像適配器-一端通過公開__iter __()和next()方法來保持for循環快樂,從而展示了迭代器協議。但是,在另一端,它僅運行該函數以從中獲取下一個值,然後將其放回暫停模式。 為什麼使用發電機? 通常,您可以編寫不使用生成器但實現相同邏輯的代碼。一種選擇是使用我之前提到的臨時列表“技巧”。並非在所有情況下都可行,例如如果您有無限循環,或者當列表很長時,它可能會導致內存使用效率低下。另一種方法是實現一個新的可迭代類SomethingIter,該類將狀態保留在實例成員中,並在next()(或Python 3中的__next __())方法中執行下一個邏輯步驟。根據邏輯的不同,next()方法內部的代碼可能看起來非常複雜並且容易出現錯誤。在這裡,發電機提供了一種干淨而簡單的解決方案。 | 這樣想: 迭代器只是一個具有next()方法的對象的美化名詞。因此,由yield產生的函數最終是這樣的: 原始版本: def some_function(): 對於我在xrange(4)中: 讓我 對於我在some_function()中: 打印我 這基本上是Python解釋器對上面的代碼所做的: 分類: def __init __(): #從-1開始,以便在下面加1時得到0。 self.count = -1 #__iter__方法將被“ for”循環調用一次。 #其餘的魔術發生在此方法返回的對像上。 #在這種情況下,它是對象本身。 def __iter __(自己): 返回自我 #下一個方法將被'for'循環重複調用 #直到引發StopIteration。 def next(self): self.count + = 1 如果self.count <4: 返回self.count 其他: #引發StopIteration異常 #表示迭代器已完成。 #這被“ for”循環隱式捕獲。 提高StopIteration def some_func(): 把它返還() 對於我在some_func()中: 打印我 要了解幕後發生的事情,可以將for循環重寫為: 迭代器= some_func() 嘗試: 而1: 打印iterator.next() StopIteration除外: 通過 這樣做更有意義還是會讓您更加困惑? :) 我應該指出,出於說明目的,這過於簡單了。 :) | yield關鍵字簡化為兩個簡單事實: 如果編譯器在函數內部的任何位置檢測到yield關鍵字,則該函數不再通過return語句返回。相反,它立即返回一個懶惰的“待處理列表”對象,稱為生成器 生成器是可迭代的。什麼是可迭代的?就像列表,集合,範圍或字典視圖一樣,它具有用於以特定順序訪問每個元素的內置協議。 簡而言之:生成器是一個懶惰的,增量待定的列表,並且yield語句允許您使用函數符號來編程生成器應逐漸吐出的列表值。 generator = myYieldingFunction(...) x =列表(發電機) 發電機 v [x [0],...,???] 發電機 v [x [0],x [1],...,???] 發電機 v [x [0],x [1],x [2],...,???] StopIteration例外 [x [0],x [1],x [2]]完成 列表== [x [0],x [1],x [2]] 例 讓我們定義一個函數makeRange,就像Python的range一樣。調用makeRange(n)返回一個生成器: def makeRange(n): #返回0,1,2,...,n-1 我= 0 而我 >> makeRange(5) <生成器對象makeRange位於0x19e4aa0> 要強制生成器立即返回其待處理的值,可以將其傳遞到list()中(就像您可以進行任何迭代一樣): >>>列表(makeRange(5)) [0,1,2,3,4] 將示例與“僅返回列表”進行比較 可以將上面的示例視為僅創建一個列表,並將其附加並返回: #list-version##generator-version def makeRange(n):#def makeRange(n): “”“返回[0,1,2,...,n-1]”“”#〜“”“返回0,1,2,...,n-1”“” TO_RETURN = []#> i = 0#i = 0 當我 >>> makeRange(5) [0,1,2,3,4] 但是,有一個主要區別。請參閱最後一節。 您如何使用發電機 可迭代是列表理解的最後一部分,並且所有生成器都是可迭代的,因此經常像這樣使用它們: #_ITERABLE_ >>> [x + 10 for makeRange(5)中的x] [10、11、12、13、14] 為了更好地了解生成器,可以使用itertools模塊(一定要使用chain.from_iterable,而不是有保證的情況下使用chain)。例如,您甚至可以使用生成器來實現無限長的惰性列表,例如itertools.count()。您可以實現自己的def enumerate(iterable):zip(count(),iterable),或者在while循環中使用yield關鍵字來實現。 請注意:生成器實際上可以用於更多事情,例如實現協程或不確定性編程或其他優雅的事情。但是,我在這裡提出的“惰性列表”觀點是您會發現的最常見用法。 幕後花絮 這就是“ Python迭代協議”的工作方式。也就是說,執行list(makeRange(5))時發生了什麼。這就是我之前所說的“懶惰的增量列表”。 >>> x = iter(範圍(5)) >>>下一個(x) >>>下一個(x) 1個 >>>下一個(x) 2 >>>下一個(x) 3 >>>下一個(x) 4 >>>下一個(x) 追溯(最近一次通話): <模塊>中第1行的文件“ ” StopIteration 內置函數next()僅調用對象.next()函數,該函數是“迭代協議”的一部分,可以在所有迭代器上找到。您可以手動使用next()函數(以及迭代協議的其他部分)來實現奇特的事情,通常是以犧牲可讀性為代價的,因此請避免這樣做... 細節 通常,大多數人不會關心以下區別,並且可能想在這裡停止閱讀。 用Python來說,可迭代是“理解for循環的概念”的任何對象,例如列表[1,2,3],而迭代器是所請求的for循環的特定實例,例如[1,2,3] ,3] .__ iter __()。生成器與任何迭代器完全相同,但其編寫方式(帶有函數語法)除外。 當您從列表中請求迭代器時,它將創建一個新的迭代器。但是,當您從迭代器請求迭代器時(很少這樣做),它只會為您提供自身的副本。 因此,在極少數情況下,您可能無法執行此類操作... > x = myRange(5) >清單(x) [0,1,2,3,4] >清單(x) [] ...然後記住生成器是迭代器;也就是說,它是一次性的。如果要重用它,則應再次調用myRange(...)。如果需要兩次使用結果,請將結果轉換為列表並將其存儲在變量x = list(myRange(5))中。那些絕對需要克隆生成器的人(例如,正在進行駭人的駭人的元編程的人)可以在絕對必要的情況下使用itertools.tee,因為可複制的迭代器Python PEP標準建議已被推遲。 | yield關鍵字在Python中有什麼作用? 答案大綱/摘要 具有yield的函數在調用時將返回Generator。 生成器是迭代器,因為它們實現了迭代器協議,因此您可以對其進行迭代。 也可以向生成器發送信息,使它在概念上成為協程。 在Python 3中,您可以使用yield from從兩個方向的一個生成器委託另一個生成器。 (附錄對幾個答案進行了評論,包括最上面的一個,並討論了生成器中return的用法。) 發電機: yield僅在函數定義內部合法,並且yield包含在函數定義中使其返回生成器。 生成器的想法來自具有不同實現方式的其他語言(請參見腳註1)。在Python的Generators中,代碼的執行會在收益率點凍結。調用生成器時(下面將討論方法),恢復執行,然後凍結在下一個產量。 產量提供了 實現迭代器協議的簡便方法,由以下兩種方法定義: __iter__和下一個(Python 2)或__next__(Python 3)。這兩種方法 將對象設為可以使用Iterator Abstract Base進行類型檢查的迭代器 來自collections模塊的類。 >>> def func(): ...產生“我是” ...產生“發電機!” ... >>> type(func)#具有yield的函數仍然是一個函數 <類型'功能'> >>> gen = func() >>> type(gen)#但它返回一個生成器 <類型'發電機'> >>> hasattr(gen,'__iter__')#這是可迭代的 真正 >>> hasattr(gen,'next')#並帶有.next(Python 3中的.__ next__) True#實現迭代器協議。 生成器類型是迭代器的子類型: >>>導入集合,類型 >>> issubclass(types.GeneratorType,collections.Iterator) 真正 並且如有必要,我們可以像這樣進行類型檢查: >>> isinstance(gen,types.GeneratorType) 真正 >>> isinstance(gen,collections.Iterator) 真正 迭代器的一個功能是,一旦耗盡,就無法重用或重置它: >>>列表(gen) ['我是,'發電機!'] >>>列表(gen) [] 如果要再次使用其功能,則必須另做一個(請參見腳註2): >>>列表(func()) ['我是,'發電機!'] 一個人可以通過編程產生數據,例如: def func(an_iterable): 對於an_iterable中的項目: 產量項目 上面的簡單生成器也等效於下面的生成器-從Python 3.3開始(在Python 2中不可用),您可以使用yield from: def func(an_iterable): 來自an_iterable的收益 但是,收益來自還允許委派給子發電機, 這將在以下有關帶有子協程的合作授權的部分中進行說明。 協程: yield形成一個表達式,該表達式允許將數據發送到生成器中(請參見腳註3) 這是一個示例,請注意接收到的變量,該變量將指向發送到生成器的數據: def bank_account(存款,利率): 而True: 計算的利息=利率*存入 收到=收益計算所得的利息 如果收到: 存入+ =已收到 >>> my_account = bank_account(1000,.05) 首先,我們必須使用內置函數將生成器排隊。它會 根據版本的不同,調用相應的next或__next__方法 您正在使用的Python: >>> first_year_interest = next(my_account) >>> first_year_interest 50.0 現在我們可以將數據發送到生成器中。 (不發送是 與調用next相同。): >>> next_year_interest = my_account.send(first_year_interest + 1000) >>> next_year_interest 102.5 合作派遣到協程,收益來自 現在,回想一下,從收益率可在Python 3。這使我們能夠協同程序委派到subcoroutine: def money_manager(expected_rate): #必須從.send()接收存款值: under_management = yield#yield沒有開始。 而True: 嘗試: 另外的投資=預期收益率*管理不足 如果額外投資: 管理欠佳+ =額外投資 除了GeneratorExit: '''TODO:寫函數發送無人認領的資金狀態''' 提高 最後: '''待辦事項:編寫功能以將稅收信息郵寄給客戶''' def investment_account(已存款,經理): '''非常簡單的委託給經理的投資賬戶模型''' #必須讓管理員排隊: next(manager)#<-與manager.send相同(無) #在這裡我們將初始存款發送給經理: manager.send(存款) 嘗試: 經理收益 除了GeneratorExit: 返回manager.close()#委託? 現在我們可以將功能委託給子生成器,並且可以使用它 由上面的發電機產生: my_manager = money_manager(.06) my_account = investment_account(1000,my_manager) first_year_return = next(my_account)#-> 60.0 現在模擬向該帳戶再添加1,000,再加上該帳戶的收益(60.0): next_year_return = my_account.send(first_year_return + 1000) next_year_return#123.6 您可以在PEP 380中閱讀有關yield的精確語義的更多信息。 其他方法:關閉並拋出 close方法在功能上提高GeneratorExit 執行被凍結。這也會被__del__調用,因此您 可以將任何清理代碼放在您處理GeneratorExit的位置: my_account.close() 您還可以引發可以在生成器中處理的異常 或傳播回用戶: 導入系統 嘗試: 引發ValueError 除了: my_manager.throw(* sys.exc_info()) 籌款: 追溯(最近一次通話): <模塊>中第4行的文件“ ” money_manager中的第6行的文件“ ” <模塊>中第2行的文件“ ” ValueError 結論 我相信我已經涵蓋了以下問題的各個方面: yield關鍵字在Python中有什麼作用? 事實證明,收益很大。我確定我可以添加更多 詳盡的例子。如果您想要更多或有建設性的批評,請通過評論讓我知道 下面。 附錄: 對最佳/可接受答案的評論** 僅以列表為例,它對使可迭代的內容感到困惑。請參閱上面的參考資料,但總而言之:iterable具有返回迭代器的__iter__方法。迭代器提供.next(Python 2或.__ next __(Python 3))方法,該方法由for循環隱式調用,直到引發StopIteration,一旦這樣做,它將繼續這樣做。 然後,它使用生成器表達式來描述什麼是生成器。由於生成器只是創建迭代器的一種簡便方法,因此它只會使問題感到困惑,而我們仍未進入屈服部分。 在控制發電機的排氣中,他調用.next方法,而接下來應使用內置函數。這將是一個適當的間接層,因為他的代碼在Python 3中不起作用。 Itertools?這與產量完全無關。 沒有討論yield的方法以及Python 3中新功能的產生。最高/可接受的答案是非常不完整的答案。 對生成器表達或理解中的屈服的答案的評論。 該語法當前允許列表理解中的任何表達式。 expr_stmt:testlist_star_expr(annassign | augassign(yield_expr | testlist)| ('='(yield_expr | testlist_star_expr))*) ... yield_expr:“ yield” [yield_arg] yield_arg:“來自”測試|測試清單 由於yield是一種表達,因此儘管沒有特別好的用例,但有人認為它可以用於理解或生成器表達中。 CPython核心開發人員正在討論棄用其津貼。 這是郵件列表中的相關帖子: 2017年1月30日19:05,布雷特·坎農寫道: 2017年1月29日星期日,克雷格·羅德里格斯(Craig Rodrigues)在星期日寫道: 兩種方法我都可以。保持Python 3中的狀態 不好,恕我直言。 我的投票是SyntaxError,因為您沒有從中得到期望 語法。 我同意這是我們最終的明智之地,因為任何代碼 依靠當前的行為真的太聰明了 可維護的。 在到達目的地方面,我們可能需要: 3.7中的語法警告或棄用警告 2.7.x中的Py3k警告 3.8中的SyntaxError 乾杯,尼克。 -Nick Coghlan | gmail.com上的ncoghlan |澳大利亞布里斯班 此外,還有一個懸而未決的問題(10544),似乎正說明這絕不是一個好主意(PyPy,用Python編寫的Python實現,已經在發出語法警告。) 最重要的是,直到CPython的開發人員另行告訴我們為止:不要將yield放在生成器表達式或理解中。 生成器中的return語句 在Python 2中: 在生成器函數中,return語句不允許包含expression_list。在這種情況下,簡單的返回指示生成器已完成,並將導致StopIteration升高。 expression_list基本上是由逗號分隔的任意數量的表達式-本質上,在Python 2中,您可以使用return停止生成器,但不能返回值。 在Python 3中: 在生成器函數中,return語句指示生成器已完成,並將引起StopIteration升高。返回的值(如果有)用作構造StopIteration的參數,並成為StopIteration.value屬性。 腳註 提案中引用了CLU,Sather和Icon語言 向Python介紹生成器的概念。總體思路是 函數可以保持內部狀態並產生中間值 用戶按需提供數據點。這有望在性能上出眾 其他方法,包括Python線程,在某些系統上甚至不可用。 例如,這意味著xrange對象(在Python 3中為range)不是迭代器,即使它們是可迭代的,因為它們可以被重用。像列表一樣,它們的__iter__方法返回迭代器對象。 yield最初是作為聲明引入的,這意味著它 只能出現在代碼塊中一行的開頭。 現在yield將創建一個yield表達式。 https://docs.python.org/2/reference/simple_stmts.html#grammar-token-yield_stmt 提出此更改是為了允許用戶將數據發送到生成器中,就像 一個人可能會收到它。要發送數據,必須能夠將其分配給某物,並且 為此,聲明將行不通。 | yield就像return一樣-它返回您告訴的任何內容(作為生成器)。不同之處在於,下次調用生成器時,執行從最後一次調用到yield語句開始。與return不同的是,在產生良率時不會清除堆棧幀,但是會將控制權轉移回調用方,因此其狀態將在下次調用函數時恢復。 就您的代碼而言,該函數get_child_candidates就像一個迭代器,因此當您擴展列表時,它一次向新列表添加一個元素。 list.extend調用迭代器,直到耗盡為止。對於您發布的代碼示例,返回一個元組並將其附加到列表中會更加清楚。 | 還有另外一件事要提到:yield的函數實際上不必終止。我寫了這樣的代碼: def fib(): 最後,cur = 0,1 而True: 屈服電流 最後,當前=當前,最後+當前 然後我可以在其他代碼中使用它: 對於fib()中的f: 如果some_condition:中斷 coolfuncs(f); 它確實有助於簡化某些問題,並使某些事情更易於使用。 | 對於那些偏愛簡單工作示例的人,請在此交互式Python會話中進行冥想: >>> def f(): ...產量1 ...產量2 ...產量3 ... >>> g = f() >>>對於我在g中: ...打印(i) ... 1個 2 3 >>>對於我在g中: ...打印(i) ... >>>#注意這次沒有打印任何內容 | TL; DR 代替這個: def square_list(n): the_list = []#替換 對於x在範圍(n)中: y = x * x the_list.append(y)#這些 返回the_list#行 做這個: def square_yield(n): 對於x在範圍(n)中: y = x * x 用這個生成y#。 每當您發現自己從頭開始建立清單時,請改為提供每份清單。 這是我第一次屈服。 產量是一種含糖的說法 建立一系列的東西 相同的行為: >>>對於square_list(4)中的square: ...打印(正方形) ... 1個 4 9 >>>對於square_yield(4)中的平方: ...打印(正方形) ... 1個 4 9 不同的行為: 收益是單次通過:您只能迭代一次。當函數具有收益時,我們稱其為生成器函數。而迭代器就是它返回的內容。這些術語在揭示。我們失去了容器的便利性,但獲得了按需計算且任意長的序列的功效。 產量是懶惰的,它推遲了計算。當調用它時,其中包含yield的函數實際上根本不會執行。它返回一個迭代器對象,該對象記住它從何處中斷。每次您在迭代器上調用next()(這在for循環中發生)時,執行都會向前推進到下一個收益。 return引發StopIteration並結束序列(這是for循環的自然結束)。 產量多才多藝。數據不必全部存儲在一起,可以一次存儲一次。它可以是無限的。 >>> def squares_all_of_them(): ... x = 0 ...而True: ...產量x * x ... x + = 1 ... >>>正方形= squares_all_of_them() >>>範圍內的_:(4): ...打印(下一個(正方形)) ... 1個 4 9 如果您需要多次通過並且序列不太長,只需在其上調用list()即可: >>>列表(square_yield(4)) [0,1,4,9] 明智地選擇yield,因為兩種含義都適用: 產量—生產或提供(如在農業中) ...提供系列中的下一個數據。 屈服—讓步或放棄(如在政治權力中一樣) ...放棄CPU執行,直到迭代器前進。 | 收益為您提供發電機。 def get_odd_numbers(i): 返回範圍(1,i,2) def yield_odd_numbers(i): 對於x在範圍(1,i,2)中: 產量x foo = get_odd_numbers(10) 條= yield_odd_numbers(10) 富 [1、3、5、7、9] 酒吧 <生成器對象yield_odd_numbers at 0x1029c6f50> bar.next() 1個 bar.next() 3 bar.next() 5 如您所見,在第一種情況下,foo一次將整個列表保存在內存中。包含5個元素的列表並不是什麼大問題,但是如果您想要500萬個列表,該怎麼辦?這不僅是一個巨大的內存消耗者,而且在調用函數時還花費大量時間來構建。 在第二種情況下,bar只是為您提供了一個生成器。生成器是可迭代的-這意味著您可以在for循環等中使用它,但是每個值只能被訪問一次。所有的值也不會同時存儲在存儲器中。生成器對象“記住”您上次調用它時在循環中的位置-這樣,如果您使用的是一個迭代的(例如)計數為500億,則不必計數為500億立即存儲500億個數字以進行計算。 再次,這是一個非常人為的示例,如果您真的想計數到500億,則可能會使用itertools。 :) 這是生成器最簡單的用例。如您所說,它可以用來編寫有效的排列,使用yield可以將內容推入調用堆棧,而不是使用某種堆棧變量。生成器還可以用於特殊的樹遍歷以及所有其他方式。 | 它正在返回發電機。我對Python並不是特別熟悉,但是我相信,如果您熟悉Python,它與C#的迭代器塊是一樣的東西。 關鍵思想是,編譯器/解釋器/會做一些棘手的事情,以便就調用者而言,他們可以繼續調用next(),它將繼續返回值-就像Generator方法已暫停一樣。現在顯然您不能真正地“暫停”方法,因此編譯器構建了一個狀態機,供您記住您當前所在的位置以及局部變量等的外觀。這比自己編寫迭代器要容易得多。 | 在描述如何使用生成器的許多很棒的答案中,我還沒有給出一種答案。這是編程語言理論的答案: Python中的yield語句返回一個生成器。 Python中的生成器是一個返回延續的函數(特別是一種協程,但是延續代表了一種更通用的機制來了解發生了什麼事情)。 編程語言理論中的連續性是一種更為基礎的計算,但是由於它們很難推理而且也很難實現,因此並不經常使用。但是,關於延續是什麼的想法很簡單:只是尚未完成的計算狀態。在此狀態下,將保存變量的當前值,尚未執行的操作等。然後,在稍後的某個時刻,可以在程序中調用繼續,以便將程序的變量重置為該狀態,並執行保存的操作。 以這種更一般的形式進行的連續可以兩種方式實現。以call / cc的方式,該程序的堆棧按字面意義保存,然後在調用延續時,將還原該堆棧。 在延續傳遞樣式(CPS)中,延續只是普通的函數(僅在函數是第一類的語言中),程序員明確地對其進行管理並傳遞給子例程。以這種方式,程序狀態由閉包(以及恰好在其中編碼的變量)表示,而不是駐留在堆棧中某個位置的變量。管理控制流的函數接受連續作為參數(在CPS的某些變體中,函數可以接受多個連續),並通過簡單地調用它們並隨後返回來調用它們來操縱控制流。延續傳遞樣式的一個非常簡單的示例如下: def save_file(文件名): def write_file_continuation(): write_stuff_to_file(文件名) check_if_file_exists_and_user_wants_to_overwrite(write_file_continuation) 在這個(非常簡單的)示例中,程序員保存了將文件實際寫入連續的操作(該操作可能是非常複雜的操作,需要寫很多細節),然後傳遞該連續(即,首先類關閉)到另一個進行更多處理的運算符,然後在必要時調用它。 (我在實際的GUI編程中經常使用此設計模式,這是因為它節省了我的代碼行,或更重要的是,在GUI事件觸發後管理控制流。) 在不失一般性的前提下,本文的其餘部分將連續性概念化為CPS,因為它很容易理解和閱讀。 現在讓我們談談Python中的生成器。生成器是延續的特定子類型。延續通常能夠保存計算狀態(即程序的調用堆棧),而生成器只能保存迭代器上的迭代狀態。雖然,對於發電機的某些使用情況,此定義會產生誤導。例如: def f(): 而True: 產量4 這顯然是一個合理的迭代器,其行為已得到很好的定義-每次生成器對其進行迭代時,它都會返回4(並且永遠如此)。但是在考慮迭代器時(例如,集合中的x:do_something(x)),可能不會想到原型的可迭代類型。此示例說明了生成器的功能:如果有什麼是迭代器,生成器可以保存其迭代狀態。 重申一下:連續可以保存程序堆棧的狀態,而生成器可以保存迭代的狀態。這意味著延續比生成器強大得多,但是生成器也非常簡單。它們對於語言設計人員來說更容易實現,對程序員來說也更容易使用(如果您有時間要花些時間,請嘗試閱讀並理解有關延續和call / cc的本頁)。 但是您可以輕鬆地將生成器實現(並將其概念化)為連續傳遞樣式的一種簡單的特定情況: 每當調用yield時,它都會告訴函數返回一個延續。再次調用該函數時,將從中斷處開始。因此,在偽偽代碼(即不是偽代碼,而不是代碼)中,生成器的next方法基本上如下: Generator()類: def __init __(self,iterable,generatorfun): self.next_continuation = lambda:generatorfun(可迭代) def next(self): 值,next_continuation =self.next_continuation() self.next_continuation = next_continuation 返回值 其中yield關鍵字實際上是實際生成器函數的語法糖,基本上是這樣的: def generatorfun(可迭代): 如果len(iterable)== 0: 提高StopIteration 其他: 返回(iterable [0],lambda:generatorfun(iterable [1:])) 請記住,這只是偽代碼,Python中生成器的實際實現更為複雜。但是,作為練習以了解發生了什麼,請嘗試使用連續傳遞樣式來實現生成器對象,而不使用yield關鍵字。 | 這是簡單語言的示例。我將提供高級人類概念與低級Python概念之間的對應關係。 我想對數字序列進行運算,但是我不想為創建該序列而煩惱自己,我只想著重於自己想做的運算。因此,我執行以下操作: 我打電話給你,告訴你我想要一個以特定方式產生的數字序列,讓您知道算法是什麼。此步驟對應於定義生成器函數,即包含yield的函數。 稍後,我告訴您,“好,準備告訴我數字的順序”。此步驟對應於調用生成器函數,該函數返回生成器對象。請注意,您還沒有告訴我任何數字。你只要拿起紙和鉛筆。 我問你,“告訴我下一個號碼”,然後你告訴我第一個號碼;之後,您等我要求您提供下一個電話號碼。記住您的位置,已經說過的電話號碼以及下一個電話號碼是您的工作。我不在乎細節。此步驟對應於在生成器對像上調用.next()。 …重複上一步,直到… 最終,您可能會走到盡頭。你不告訴我電話號碼;您只是大聲喊道:“抱馬!我做完了!沒有數字了!”此步驟對應於生成器對象結束其工作,並引發StopIteration異常。生成器函數不需要引發異常。函數結束或發出返回值時,它將自動引發。 這就是生成器的功能(包含yield的函數);它開始執行,在產生任何結果時暫停,並在詢問.next()值時從其最後一點繼續。根據設計,它與Python的迭代器協議完美契合,該協議描述瞭如何順序請求值。 迭代器協議最著名的用戶是Python中的for命令。因此,只要您執行以下操作: 對於順序項目: 序列是如上所述的列表,字符串,字典還是生成器對像都無關緊要;結果是一樣的:您從一個序列中逐個讀取項目。 請注意,定義包含yield關鍵字的函數不是創建生成器的唯一方法;這是創建一個的最簡單的方法。 有關更準確的信息,請閱讀Python文檔中有關迭代器類型,yield語句和生成器的信息。 | 儘管很多答案表明了為什麼要使用yield來創建生成器,但是yield有更多用途。創建協程非常容易,這使信息可以在兩個代碼塊之間傳遞。我不會重複任何關於使用yield來創建生成器的優秀示例。 為了幫助理解以下代碼中的yield,您可以用手指在任何具有yield的代碼中跟踪循環。每次您的手指觸碰良率時,您都必須等待下一個或要發送的輸入。調用next時,您將遍歷代碼,直到達到收益為止。收益率右側的代碼將被評估並返回給調用方……然後等待。再次調用next時,您將在代碼中執行另一個循環。但是,您會注意到,在協程中,yield也可以與send一起使用……它將從調用方將值發送到yield函數。如果給出了發送,則yield接收發送的值,然後將其吐出左側。然後,通過代碼進行的跟踪將一直進行,直到再次達到yield為止為止(最後返回值,就像調用了next一樣)。 例如: >>> def coroutine(): ...我= -1 ...而True: ...我+ = 1 ... val =(收益i) ... print(“收到的%s”%val) ... >>>序列= coroutine() >>> sequence.next() >>> sequence.next() 沒有收到 1個 >>> sequence.send('hello') 收到你好 2 >>> sequence.close() | 還有另一個yield用法和含義(自Python 3.3起): 的收益 從PEP 380-委託給子生成器的語法: 提出了一種語法,供生成器將其部分操作委託給另一生成器。這允許包含“ yield”的一段代碼被分解並放置在另一個生成器中。此外,允許子生成器返回一個值,並且該值可供委託生成器使用。 當一個生成器重新產生另一個生成器的值時,新語法還為優化提供了一些機會。 此外,這將引入(自Python 3.5起): 異步def new_coroutine(數據): ... 等待blocking_action() 為了避免將協程與常規生成器混淆(兩者目前都使用了產量)。 | 所有好的答案,但是對於新手來說有點困難。 我認為您已經了解了return語句。 打個比方,收益率和收益率是雙胞胎。 return表示“返回並停止”,而“ yield”表示“返回但繼續” 嘗試獲取帶有返回的num_list。 def num_list(n): 對於我在範圍(n)中: 還給我 運行: 在[5]中:num_list(3) 出[5]:0 看,您只會得到一個數字,而不是列表。 return永遠不會讓你高高興興,只執行一次就退出。 有產量 用收益替換收益: 在[10]中:def num_list(n): ...:對於範圍(n)中的i: ...:讓我 ...: 在[11]中:num_list(3) Out [11]:<位於0x10327c990的發電機對象num_list> 在[12]中:list(num_list(3)) 出[12]:[0、1、2] 現在,您將贏得所有數字。 比較一次運行並停止的收益率,收益運行時間是您計劃的時間。 您可以將return解釋為其中之一,並把yield解釋為全部。這稱為可迭代。 我們可以再一步用return重寫yield語句 在[15]中:def num_list(n): ...:結果= [] ...:對於範圍(n)中的i: ...:result.append(i) ...:返回結果 在[16]中:num_list(3) 出[16]:[0,1,2] 這是產量的核心。 列表返回輸出和對象收益輸出之間的區別是: 您將始終從列表對象獲取[0,1,2],但只能從“對象產量輸出”中檢索一次。因此,它具有一個新的名稱生成器對象,如Out [11]中所示:<生成器對象num_list位於0x10327c990>。 最後,作為一個隱喻,它可以: 收益率和收益率是雙胞胎 列表和發電機是雙胞胎 | 從編程的角度來看,迭代器被實現為thunk。 為了將迭代器,生成器和線程池實現為thunk,以便並發執行等,人們使用發送到具有分派器的閉包對象的消息,然後分派器對“消息”做出響應。 “下一個”是發送到閉包的消息,由“ iter”調用創建。 有很多方法可以實現此計算。我使用了變異,但是可以通過返回當前值和下一個生成器(使其參照透明)來進行這種無變異的計算。 Racket使用某些中間語言對初始程序進行一系列轉換,這種重寫之一使yield運算符可以使用更簡單的運算符轉換為某種語言。 這是使用R6RS的結構如何重寫yield的演示,但是其語義與Python相同。它是相同的計算模型,只需要更改語法即可使用Python的產量重寫它。 歡迎使用Racket v6.5.0.3。 ->(定義gen (λ(l) (定義產量 (lambda() (如果(為空?l) '結束 (讓[[v [car l))) (設置!l(cdr l)) v)))) (λ(米) (案例m ('收益率(yield)) ('init(lambda(數據) (設置!l個數據) '好)))))) ->(定義流(gen'(1 2 3))) ->(流的收益) 1個 ->(流的收益) 2 ->(流的收益) 3 ->(流的收益) '結束 ->((流'init)'(a b)) '好 ->(流的收益) '一種 ->(流的收益) 'b ->(流的收益) '結束 ->(流的收益) '結束 -> | 以下是一些Python示例,這些示例說明如何實際實現生成器,就像Python沒有為其提供語法糖一樣: 作為Python生成器: 從itertools導入islice def fib_gen(): a,b = 1,1 而True: 產生一個 a,b = b,a + b 斷言[1,1,2,3,5] == list(islice(fib_gen(),5)) 使用詞法閉包而不是生成器 def ftake(fnext,最後): 返回[在xrange(last)中_的[fnext())] def fib_gen2(): #funky作用域由於python2.x解決方法 #對於python 3.x使用非本地 def _(): _.a,_。b = _.b,_。a + _.b 返回_.a _.a,_。b = 0、1 返回_ 斷言[1,1,2,3,5] == ftake(fib_gen2(),5) 使用對象閉包而不是生成器(因為ClosuresAndObjectsAreEquivalent) 類fib_gen3: def __init __(): self.a,self.b = 1,1 def __call __(自己): r =自我 self.a,self.b = self.b,self.a + self.b 返回r 斷言[1,1,2,3,5] == ftake(fib_gen3(),5) | 我打算發布“閱讀Beazley的“ Python:基本參考”的第19頁,以快速了解生成器”,但是已經有許多其他人發布了不錯的描述。 另外,請注意,協程可以將yield用作生成函數的雙重用途。儘管(yield)與代碼段用法不同,但它可以用作函數中的表達式。當調用者使用send()方法向該方法發送值時,協程將一直執行,直到遇到下一個(yield)語句為止。 發電機和協程是設置數據流類型應用程序的一種很酷的方法。我認為值得了解函數中yield語句的其他用法。 | 這是一個簡單的示例: def isPrimeNumber(n): 打印“ isPrimeNumber({})調用”。format(n) 如果n == 1: 返回False 對於範圍(2,n)中的x: 如果n%x == 0: 返回False 返回True def素數(n = 1): while(真): 打印“循環步驟---------------- {}”。format(n) 如果isPrimeNumber(n):產生n n + = 1 對於質數()中的n: 如果n> 10:break 打印“寫結果{}”。format(n) 輸出: 循環步驟---------------- 1 isPrimeNumber(1)調用 循環步驟---------------- 2 isPrimeNumber(2)調用 循環步驟---------------- 3 isPrimeNumber(3)調用 寫結果3 循環步驟---------------- 4 isPrimeNumber(4)調用 循環步驟---------------- 5 isPrimeNumber(5)調用 寫結果5 循環步驟---------------- 6 isPrimeNumber(6)調用 循環步驟---------------- 7 isPrimeNumber(7)調用 寫結果7 循環步驟---------------- 8 isPrimeNumber(8)調用 循環步驟---------------- 9 isPrimeNumber(9)調用 循環步驟---------------- 10 isPrimeNumber(10)調用 循環步驟---------------- 11 isPrimeNumber(11)調用 我不是Python開發人員,但在我看來,yield保持程序流的位置,而下一個循環從“ yield”位置開始。好像它在那個位置上等待,就在那之前,在外面返回一個值,下一次繼續工作。 這似乎是一種有趣而又不錯的能力:D | 這是產量的心理印象。 我喜歡將線程視為具有堆棧(即使未以這種方式實現)。 調用普通函數時,它將其局部變量放在堆棧上,進行一些計算,然後清除堆棧並返回。再也看不到其局部變量的值。 使用yield函數,當其代碼開始運行時(即在調用函數後,返回生成器對象,然後調用next()方法的生成器對象),它類似地將其局部變量放到堆棧上並進行一段時間的計算。但是,當它命中yield語句時,在清除堆棧的一部分並返回之前,它會對其局部變量進行快照並將其存儲在生成器對像中。它還會在代碼中寫下當前位置(即特定的yield語句)。 因此,這是生成器掛起的一種凍結函數。 隨後調用next()時,它將函數的所有物檢索到堆棧上並對其進行動畫處理。該函數從中斷處繼續進行計算,而忽略了它剛剛在冷藏庫中度過了一個永恆的事實。 比較以下示例: def normalFunction(): 返回 如果為假: 通過 def yielderFunction(): 返回 如果為假: 產量12 當我們調用第二個函數時,它的行為與第一個函數非常不同。 yield語句可能無法到達,但是如果它存在於任何地方,它將改變我們正在處理的內容的性質。 >>> yielderFunction() <生成器對象yielderFunction at 0x07742D28> 調用yielderFunction()不會運行其代碼,但是會使代碼生成一個生成器。 (為便於閱讀,使用yielder前綴命名此類名稱可能是個好主意。) >>> gen = yielderFunction() >>> dir(gen) ['__類__', ... '__iter __',#返回gen本身,使其與容器統一工作 ...#設置為for循環時。 (容器返回一個迭代器。) '關', 'gi_code', 'gi_frame', 'gi_running', 'next',#運行函數主體的方法。 '發送', '扔'] gi_code和gi_frame字段是凍結狀態的存儲位置。用dir(..)探索它們,我們可以確認上面的心理模型是可信的。 | 一個簡單的例子來了解它是什麼:yield def f123(): 對於_範圍(4): 產量1 產量2 對於我在f123()中: 打印(i) 輸出為: 1 2 1 2 1 2 1 2 | 就像每個答案所暗示的那樣,yield用於創建序列生成器。它用於動態生成一些序列。例如,在網絡上逐行讀取文件時,可以按以下方式使用yield函數: def getNextLines(): 而con.isOpen(): 產生con.read() 您可以在代碼中使用它,如下所示: 對於getNextLines()中的行: doSomeThing(行) 執行控制轉移陷阱 執行yield時,執行控制將從getNextLines()轉移到for循環。因此,每次調用getNextLines()時,都會從上次暫停的位置開始執行。 因此,簡而言之,具有以下代碼的函數 def simpleYield(): 產生“第一次” 產生“第二次” 產生“第三次” 產生“現在有用的值{}”。format(12) 對於我在simpleYield()中: 打印我 將打印 “第一次” “第二次” “第三次” “現在有一些有用的值12” | (我下面的回答僅從使用Python生成器的角度講,而不是生成器的基礎實現機制,其中涉及一些堆棧和堆操作的技巧。) 當使用yield而不是python函數中的返回值時,該函數將變成一種特殊的生成器函數。該函數將返回生成器類型的對象。 yield關鍵字是一個標誌,用於通知python編譯器專門對待此類功能。普通函數將在返回一些值後終止。但是在編譯器的幫助下,可以將generator函數視為可恢復的。也就是說,將恢復執行上下文,並且將從上次運行繼續執行。在顯式調用return之前,它將引發StopIteration異常(這也是迭代器協議的一部分),或者到達函數的結尾。我發現了很多有關生成器的參考,但是從功能編程的角度來看,這是最易理解的。 (現在,我想根據自己的理解來討論generator的基本原理,以及迭代器。我希望這可以幫助您掌握迭代器和generator的基本動機。這種概念在其他語言(例如C#)中也會出現。) 據我了解,當我們要處理一堆數據時,通常會先將數據存儲在某個地方,然後再逐一處理。但是這種幼稚的方法是有問題的。如果數據量巨大,則預先存儲它們是很昂貴的。因此,為什麼不間接存儲某種類型的元數據,而不是直接存儲數據本身,即邏輯如何計算數據。 有兩種包裝此類元數據的方法。 面向對象的方法,我們將元數據包裝為一個類。這就是實現迭代器協議的所謂迭代器(即__next __()和__iter __()方法)。這也是常見的迭代器設計模式。 在功能方法上,我們將元數據包裝為一個函數。這是 所謂的發電機功能。但實際上,返回的生成器對象仍然是IS-A迭代器,因為它也實現了迭代器協議。 無論哪種方式,都會創建一個迭代器,即某個可以為您提供所需數據的對象。 OO方法可能有點複雜。無論如何,使用哪個取決於您。 | 總而言之,yield語句將您的函數轉換為一個工廠,該工廠產生一個稱為生成器的特殊對象,該對象環繞原始函數的主體。迭代生成器後,它將執行您的函數,直到達到下一個yield為止,然後掛起執行並評估傳遞給yield的值。它將在每次迭代中重複此過程,直到執行路徑退出函數為止。例如, def simple_generator(): 產生一個 產生“兩個” 產生“三” 對於我在simple_generator()中: 打印我 簡單地輸出 一 二 三 動力來自將生成器與計算序列的循環配合使用,生成器每次執行循環都會停止,以“屈服”下一個計算結果,這樣就可以即時計算列表,好處是存儲保存用於特別大的計算 假設您要創建自己的範圍函數以產生可迭代的數字範圍,則可以這樣做, def myRangeNaive(i): n = 0 範圍= [] 而n >> fib() <生成器對象fib位於0x7fa38394e3b8> 這是因為yield的存在向Python發出信號,要求您創建一個生成器,即一個按需生成值的對象。 那麼,如何生成這些值?可以直接使用下一步的內置函數來完成此操作,也可以通過將其提供給使用值的構造來間接完成此操作。 使用內置的next()函數,您可以直接調用.next / __ next__,強制生成器生成一個值: >>> g = fib() >>>下一個(g) 1個 >>>下一個(g) 1個 >>>下一個(g) 2 >>>下一個(g) 3 >>>下一個(g) 5 間接地,如果將fib提供給for循環,列表初始化程序,元組初始化程序或其他任何期望對像生成/產生值的對象,則將“消耗”生成器,直到不能再產生更多值為止(它返回): 結果= [] 對於我在fib(30)中:#消耗fib results.append(i) #也可以用 結果= list(fib(30))#消耗fib 同樣,使用元組初始化程序: >>>元組(fib(5))#消耗fib (1、2、3、5) 生成器在延遲方面與功能有所不同。它通過保持其本地狀態並允許您在需要時恢復來實現此目的。 首次調用fib時: f = fib() Python編譯函數,遇到yield關鍵字,然後簡單地將生成器對象返回給您。看起來不是很有幫助。 然後,當您請求它直接或間接生成第一個值時,它將執行找到的所有語句,直到遇到yield為止,然後將您提供的值返回給yield並暫停。為了更好地說明這一點,讓我們使用一些打印調用(如果在Python 2上,請用打印“文本”代替): def yielder(值): “”“這是一個無限生成器。只能在其上使用next 而1: 打印(“我將為您創造價值”) print(“然後我會停一會兒”) 產量值 打印(“讓我們再次遍歷它。”) 現在,輸入REPL: >>> gen = yielder(“ Hello,yield!”) 您現在有了一個生成器對象,等待一個命令來生成一個值。接下來使用並查看打印出來的內容: >>> next(gen)#運行直到找到產量 我將為您創造價值 那我停一會兒 “你好,屈服!” 未報價的結果是所打印的內容。引用的結果是從yield返回的結果。現在再次撥打電話: >>> next(gen)#從產量繼續運行並再次運行 讓我們再經歷一次。 我將為您創造價值 那我停一會兒 “你好,屈服!” 生成器記住它已停頓在yield值上,然後從那裡恢復。打印下一條消息,並再次執行搜索yield語句以使其暫停的消息(由於while循環)。 | 1個 2 下一個 高度活躍的問題。贏得10個聲譽才能回答這個問題。信譽要求有助於保護該問題免受垃圾郵件和非答復活動的侵害。 不是您要找的答案?瀏覽其他問題標記為python的迭代器生成器產生協程或詢問您自己的問題。